﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Odyssey.Utils;
using System.Linq;
using System.Collections.Specialized;
using System.Collections;
using System.Windows.Threading;
using System.Diagnostics;
using Odyssey.Controls.Internals;
using System.Collections.Generic;

namespace Odyssey.Controls.Primitives
{
    /// <summary>
    /// Panel for IItemsControl that renders all items vertically as rows with a fixed item height by virtualizing the items.
    /// </summary>
    public class VirtualizingRowPanel : OdcVirtualizingPanel
    {
        const bool recycle = true;
        #region fields

        private double verticalOffset = -1;
        private ScrollViewer scrollViewer;
        private Range currentRange = new Range(0, -1);
        private int totalItemsCount = 0;
        private DispatcherTimer timer;

        #endregion
        #region ctor

        public VirtualizingRowPanel()
            : base()
        {
            this.Loaded += new RoutedEventHandler(OnLoaded);
            this.Unloaded += new RoutedEventHandler(OnUnloaded);
        }


        #endregion
        #region attached props



        public static TimeSpan GetInterval(DependencyObject obj)
        {
            return (TimeSpan)obj.GetValue(IntervalProperty);
        }

        public static void SetInterval(DependencyObject obj, TimeSpan value)
        {
            obj.SetValue(IntervalProperty, value);
        }

        public static readonly DependencyProperty IntervalProperty =
            DependencyProperty.RegisterAttached("Interval", typeof(TimeSpan), typeof(VirtualizingRowPanel), new PropertyMetadata(TimeSpan.FromMilliseconds(1)));


        #endregion
        #region methods

        protected internal override void Reset()
        {
            StopTimer();
            //totalItemsCount = 0;
            ResetItems();
            base.Reset();
        }


        /// <summary>
        /// Gets or sets the height that is expected to be available when getting measured.
        /// This used when the actual height of the ItemsHost is not yet determined.
        /// </summary>
        public double ExpectedHeight
        {
            get { return (double)GetValue(ExpectedHeightProperty); }
            set { SetValue(ExpectedHeightProperty, value); }
        }

        public static readonly DependencyProperty ExpectedHeightProperty =
            DependencyProperty.Register("ExpectedHeight", typeof(double), typeof(ItemsControl), new PropertyMetadata(800.0));




        protected override void OnLoaded(object sender, RoutedEventArgs e)
        {
            scrollViewer = this.GetVisualAncestors().OfType<ScrollViewer>().FirstOrDefault();
            if (scrollViewer != null)
            {
                CompositionTarget.Rendering += OnRender;
            }
            base.OnLoaded(sender, e);
        }

        protected override void OnUnloaded(object sender, RoutedEventArgs e)
        {
            base.OnUnloaded(sender, e);
            CompositionTarget.Rendering -= OnRender;
            StopTimer();
        }


        private void OnRender(object sender, EventArgs e)
        {
            double offset = scrollViewer.VerticalOffset;
            if (this.verticalOffset != offset)
            {
                this.verticalOffset = offset;

                double h = ItemsControl.ActualHeight;
                if (h == 0) h = ExpectedHeight;
                Range range = DetermineIndexRange(h);
                if (range != currentRange)
                {
                    Balance(range);
                }
            }
        }


        protected void Balance()
        {
            double h = ItemsControl.ActualHeight;
            if (h == 0) h = ExpectedHeight;
            Balance(DetermineIndexRange(h));
        }


        /// <summary>
        /// Determines the required children based on the layout and retrieves it's content from IItemsHost:
        /// </summary>
        protected void Balance(Range range)
        {

            IsInvalidated = false;
            Range previousRange = currentRange;
            currentRange = range;
            //if (previousRange != currentRange)
            {
                IItemsControl itemsControl = ItemsControl;
                if (scrollViewer != null) this.verticalOffset = scrollViewer.VerticalOffset;

                bool isLoading = false;
                if (itemsControl.IsIntervalEnabled)
                {
                    isLoading = ManageInterval();
                }
                if (recycle) RecycleElementsOutOfRange(previousRange, currentRange, true);
                int childIndex = 0;


                double itemHeight = itemsControl.ItemHeight;
                Size size = new Size(this.ActualWidth, itemHeight);

                Rect rect = new Rect(0.0, 0d, this.ActualWidth, itemHeight);
                for (int i = currentRange.From; i <= currentRange.To; i++)
                {
                    bool isNew;
                    FrameworkElement item = itemsControl.GetItemContainer(i, out isNew);
                    if (item != null)
                    {
                        if (isNew)
                        {
                            childIndex = AddOrInsertItem(previousRange.To, childIndex, i, item);
                        }
                    }
                }
            }
        }

        private bool ManageInterval()
        {
            bool isLoading = false;
            int count = currentRange.Count;
            if (totalItemsCount <= count)
            {
                isLoading = true;
                currentRange.To = currentRange.From + totalItemsCount;
                StartTimer();
            }
            else
            {
                StopTimer();
            }
            return isLoading;
        }

        private int AddOrInsertItem(int itemIndex, int childIndex, int i, UIElement item)
        {
#if DEBUG
            if (Children.Contains(item))
            {
                throw new IndexOutOfRangeException(itemIndex.ToString());
            }
#endif

            if (i >= itemIndex)
            {
                Children.Add(item);
            }
            else
            {
                Children.Insert(childIndex++, item);
            }
            return childIndex;
        }


        protected override Size MeasureOverride(Size availableSize)
        {
            double totalHeight = availableSize.Height;
            double totalWidth = availableSize.Width;

            IItemsControl itemsControl = ItemsControl;
            int count = itemsControl.ItemsSource != null ? itemsControl.ItemsSource.Count : 0;
            if (double.IsInfinity(totalHeight)) totalHeight = itemsControl.ItemHeight * count;
            if (double.IsInfinity(totalWidth)) totalWidth = 800d;

            if (IsInvalidated) Balance();
            Size size = new Size(totalWidth, itemsControl.ItemHeight);

            foreach (var child in Children) child.Measure(availableSize);

            //if (!double.IsInfinity(availableSize.Height)) totalHeight = Math.Max(availableSize.Height, totalHeight);
            return new Size(totalWidth, totalHeight);
        }

        protected override Size ArrangeOverride(Size finalSize)
        {

            IItemsControl itemsControl = ItemsControl;
            double itemHeight = itemsControl.ItemHeight;
            Rect rect = new Rect(0.0, recycle ? currentRange.From * itemHeight : 0d, finalSize.Width, itemHeight);
            foreach (var c in Children)
            {
                c.Arrange(rect);
                rect.Y += itemHeight;
            }

            return finalSize;
        }


        /// <summary>
        /// Get the range of items which are currently in the visible view.
        /// </summary>
        /// <param name="height">The height of the IItemsControl which hosts this panel.</param>
        /// <returns>Range of Item indexes inside the visible view.</returns>
        protected virtual Range DetermineIndexRange(double height)
        {
            IItemsControl itemsControl = ItemsControl;
            if (itemsControl == null) return new Range(0, -1);

            //            double top = scrollViewer != null ? scrollViewer.VerticalOffset : 0d;

            Point pt;
            try
            {
                var gt = this.TransformToVisual(itemsControl as UIElement);
                gt.TryTransform(new Point(), out pt);
            }
            catch
            {
                pt = new Point();
            };

            double top = -pt.Y;

            double itemHeight = itemsControl.ItemHeight;
            double bottom = top + height;

            int itemCount = itemsControl.ItemsSource != null ? itemsControl.ItemsSource.Count : 0;

            return new Range(
                Math.Max(0, (int)Math.Floor((top - itemHeight - 1) / itemHeight)),
                Math.Min((int)Math.Ceiling((bottom) / itemHeight), itemCount - 1)
                );


        }

        protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Reset:
                    ResetItems();
                    break;

                case NotifyCollectionChangedAction.Replace:
                    Invalidate();
                    break;

                case NotifyCollectionChangedAction.Add:
                    AddItems(e.NewItems, e.NewStartingIndex);
                    break;

                case NotifyCollectionChangedAction.Remove:
                    RemoveItems(e.OldItems, e.OldStartingIndex);
                    break;
            }
        }

        private void AddItems(IList addedItems, int startingIndex)
        {
            int to = currentRange.To;
            if (startingIndex <= to || to < 0)
            {
                RecycleItems(startingIndex);
            }
            Invalidate();
        }


        private void RemoveItems(IList removedItems, int startingIndex)
        {
            if (startingIndex <= currentRange.To)
            {
                RecycleItems(startingIndex);
                Invalidate();
            }
        }

        private void RecycleItems(int startingIndex)
        {
            int from = Math.Max(0, startingIndex - currentRange.From);
            var itemsControl = ItemsControl;
            for (int i = Children.Count - 1; i >= from; i--)
            {
                Children.RemoveAt(i);
                itemsControl.ReleaseItemContainer(i + currentRange.From);
            }
            currentRange.To = from;
        }


        private void ResetItems()
        {
            StopTimer();
            totalItemsCount = 0;
            int n = Children.Count + currentRange.From;
            var itemsControl = ItemsControl;
            for (int i = currentRange.From; i < n; i++) itemsControl.ReleaseItemContainer(i);
            Children.Clear();
            currentRange.To = -1;
            verticalOffset = 0;
            Invalidate();
        }

        private void StopTimer()
        {
            if (timer != null && timer.IsEnabled)
            {
                timer.Stop();
                timer = null;
                totalItemsCount = int.MaxValue;
            }
        }

        private void StartTimer(bool restart = false)
        {
            if (restart || timer == null)
            {
                StopTimer();
                totalItemsCount = 0;

                timer = new DispatcherTimer();



                timer.Interval = ItemsControl != null ? GetInterval(this.ItemsControl as DependencyObject) : TimeSpan.FromMilliseconds(1d);
                timer.Tick += new EventHandler(OnTimerTick);
                timer.Start();
            }

        }

        private void OnTimerTick(object sender, EventArgs e)
        {
            totalItemsCount++;
            Balance();
        }

        public override void ScrollIntoView(int itemIndex)
        {
            if (scrollViewer != null)
            {
                double itemHeight = ItemsControl.ItemHeight;
                double from = scrollViewer.VerticalOffset;
                double to = from + ItemsControl.ActualHeight - itemHeight;
                double offset = itemHeight * itemIndex;
                if (offset < from || offset > to)
                {
                    if (offset > to)
                    {
                        offset = offset - ItemsControl.ActualHeight + itemHeight;
                    }
                    scrollViewer.ScrollToVerticalOffset(offset);
                }
            }
        }




        #endregion


    }
}
